package codename.carrot.bomberman.network.server;

import java.util.concurrent.CopyOnWriteArrayList;

import android.util.Log;
import codename.carrot.bomberman.game.gamemessages.PlayerQuitMessage;
import codename.carrot.bomberman.network.ConnectionThread;
import codename.carrot.bomberman.network.MessageHandler;
import codename.carrot.bomberman.network.MessageHandler.MessageReceiver;
import codename.carrot.bomberman.network.MessageHandler.MessageType;
import codename.carrot.bomberman.network.NetworkConnection;
import codename.carrot.bomberman.network.NetworkConnection.NetworkState;

/**
 * This is the maintaining class of all connections made and set up by the
 * server.
 * 
 * @author Group A4.
 * 
 */
public class ServerConnection {

	/**
	 * String for debugging.
	 */
	private String DEBUG = this.getClass().getSimpleName();
	/**
	 * The pool of client connections.
	 */
	private CopyOnWriteArrayList<ConnectionThread> connectionPool = new CopyOnWriteArrayList<ConnectionThread>();
	/**
	 * The server used for setting up connections.
	 */
	private Server server;
	/**
	 * The message handler.
	 */
	private MessageHandler messageHandler;
	/**
	 * The network state of the server connection.
	 */
	private NetworkState state = NetworkState.NONE;

	/**
	 * Constructs a server connection.
	 * 
	 * @param messageHandler
	 *            The message handler.
	 */
	public ServerConnection(MessageHandler messageHandler) {
		this.messageHandler = messageHandler;
		Log.d(DEBUG, "Starting as server.");
		terminate();
		// ADD A NEW SERVERCONNECTION AS SERVER!
		connectionPool.add(new ServerConnectionThread(this.getNumberOfConnections(), this.messageHandler));
		this.setState(NetworkState.LISTENING);
		server = new Server(this, messageHandler);
		server.start();
		Log.d(DEBUG, "Connections: " + connectionPool.size());
	}

	/**
	 * Terminate a spesific client connection.
	 * 
	 * @param clientId
	 *            The ID of the connection to be terminated.
	 */
	public void terminateClientConnection(int clientId) {
		Log.d(DEBUG, "Terminating clientID: " + clientId);
		if (!connectionPool.isEmpty()) {
			ConnectionThread connectionToClient = null;
			for (ConnectionThread c : connectionPool) {
				if (c.getClientID() == clientId) {
					connectionToClient = c;
					connectionToClient.terminate();
					Log.d(DEBUG, "Found client connection. Terminated.");
				}
			}
			if (connectionToClient != null) {
				connectionPool.remove(connectionToClient);
				// ----GAME----
				Log.d(DEBUG, "Connections: " + this.getNumberOfConnections());
				Log.d(DEBUG, "Connection state: " + this.state.toString());
				Log.d(DEBUG, "Sending player quit message");
				PlayerQuitMessage playerQuitMessage = new PlayerQuitMessage(
						connectionToClient.getClientID());
				ServerPostOffice.getInstance()
						.sendMessage(playerQuitMessage, 0);
				// -----------
			}
			if (this.getNumberOfConnections() < 3
					&& this.state == NetworkState.CONNECTED) {
				Log.d(DEBUG, "Server quit number of connections too low");
				NetworkConnection.getInstance().terminate();
			}
		}
	}

	/**
	 * Call to terminate all connections and empty the connection pool.
	 */
	public void terminate() {
		Log.d(DEBUG, "Terminate network connections.");
		if (server != null) {
			server.terminate();
			server = null;
		}

		if (!connectionPool.isEmpty()) {
			for (ConnectionThread c : connectionPool) {
				c.terminate();
			}
			connectionPool.clear();
		}

		this.setState(NetworkState.NONE);
	}

	/**
	 * Cancel accept on the server and set the connection to connected.
	 */
	public void cancelAccept() {
		Log.d(DEBUG, "Canceling accept");
		if (server != null) {
			if (getNumberOfConnections() > 1)
				this.setState(NetworkState.CONNECTED);
			else
				this.terminate();
			server.cancelAccept();
		}
	}

	/**
	 * The number of connections currently in the connection pool.
	 * 
	 * @return The number of connections
	 */
	public int getNumberOfConnections() {
		return connectionPool.size();
	}

	/**
	 * Set the current connection state for the server.
	 * 
	 * @param state
	 *            The new state.
	 */
	protected void setState(NetworkState state) {
		if (this.state != state) {
			Log.d(DEBUG, "STATE: " + this.state + " -> " + state);
			messageHandler.obtainMessage(MessageType.STATE, state)
					.sendToTarget();
			this.state = state;
		}
	}

	/**
	 * Add a connection to the pool.
	 * 
	 * @param connectionThread
	 *            The connection thread to add to the pool.
	 */
	public void addConnection(ConnectionThread connectionThread) {
		connectionPool.add(connectionThread);
		connectionThread.start();
		Log.d(DEBUG, "Added new connection. Number of connections: "
				+ getNumberOfConnections());
		this.setState(NetworkState.LISTENING_CONNECTED);
		if ((server != null && getNumberOfConnections() >= NetworkConnection.MAX_CONNECTIONS)) {
			Log.d(DEBUG, "Max connections reached");
			cancelAccept();
		}

	}

	/**
	 * Send a message to a connection.
	 * 
	 * @param object
	 *            The object to send.
	 * @param clientTo
	 *            Send to this client id.
	 * @param clientFrom
	 *            Send from this client id.
	 * @param messageReceiver
	 *            The state to send - Including (send to all), Excluding (send
	 *            to all except the client in clientTo), Independent (send only
	 *            to the client in clientTo).
	 */
	public void send(Object object, int clientTo, int clientFrom,
			MessageReceiver messageReceiver) {
		Log.d(DEBUG, "Sending " + messageReceiver.toString() + " - Client: "
				+ clientTo);
		switch (messageReceiver) {
		case INCLUSIVE:
			for (ConnectionThread connectionThread : connectionPool) {
				if (connectionThread.getClientID() != clientFrom)
					connectionThread.send(object);
			}
			break;
		case INDEPENDENT:
			for (ConnectionThread connectionThread : connectionPool) {
				if (connectionThread.getClientID() == clientTo)
					connectionThread.send(object);
			}
			break;
		case EXCLUSIVE:
			for (ConnectionThread connectionThread : connectionPool) {
				if (connectionThread.getClientID() != clientTo
						&& connectionThread.getClientID() != clientFrom)
					connectionThread.send(object);
			}
			break;
		}
	}

}
